/**
 * 模块作用与原理说明：
 * - 本服务用于在 NestJS 中通过 CASL 构建权限“能力”（Ability，对象化的授权规则集合）。
 * - CASL（@casl/ability）是一个基于“动作(action)”和“主题(subject)”的访问控制库。通过 AbilityBuilder
 *   可以以声明式的方式定义规则（例如：can('read', 'Article')）。
 * - 此处使用 createMongoAbility，表示能力规则支持 Mongo 风格的条件（如 { authorId: user.id }），
 *   便于后续对资源对象进行基于属性的细粒度授权判断。
 *
 * 关键概念：
 * - action：字符串或枚举，表示要执行的操作，如 'read'、'update'、'delete'、'manage'。
 * - subject：字符串或类名，表示资源类型，如 'User'、'Article'，也可以使用 'all' 指代所有资源。
 * - manage：CASL 的保留动作，表示对某资源拥有所有操作权限；搭配 'all' 代表对所有资源拥有所有操作权限。
 * - detectSubjectType：用于从对象实例自动推断 subject 类型，这里通过 item.constructor.name 获取类名。
 *
 * 使用方式（示例，仅注释演示，不改变业务逻辑）：
 * 1) 在模块中注入并获取 Ability：
 *    const ability = caslAbilityService.forRoot();
 * 2) 在守卫或拦截器中判断权限：
 *    if (!ability.can('update', 'User')) { throw new ForbiddenException(); }
 *    // 当传入具体实例对象时（如某个 Article 实例），可依赖 detectSubjectType 自动识别类型：
 *    ability.can('read', someArticleInstance) // 将根据构造函数名识别为 'Article'
 *
 * 注意：
 * - 当前定义为 can('manage', 'all')，表示“拥有对所有资源的所有操作权限”，适合系统超级管理员场景。
 */

import { Injectable } from "@nestjs/common";
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
import { Logs } from "../logs/logs.entity";
import { UserService } from "../user/user.service";
import { getEntitiesByPath } from "src/utils/common";

@Injectable()
export class CaslAbilityService {
  constructor(private userService: UserService) {}
  /**
   * 构建并返回一个 CASL 能力对象（Mongo 风格能力）。
   *
   * 返回的 ability 拥有全局管理权限（manage all），即对所有 subject 具有所有操作的许可。
   * detectSubjectType 指定如何从对象实例推断 subject 类型，这里取构造函数名作为 subject。
   *
   * 推荐用法：
   * - 在具有全局权限的上下文（如超级管理员）直接返回该能力；
   * - 如果需要不同角色的能力，请在此处根据用户角色/上下文动态调整 can(...) 规则。
   */
  async forRoot(username: string) {
    // AbilityBuilder 用于声明授权规则；createMongoAbility 指定能力类型支持 Mongo 风格条件
    const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

    // 其他思路:acl-> 表来进行存储-> LogController + Action
    // log->sys:log-> sys:log:read，sys:log:write
    // 定义授权规则：对所有资源（'all'）执行所有操作（'manage'）均被允许
    // can("manage", "all");
    const user = await this.userService.find(username);
    // 1 user -> n roles    1 roles -> n menus   需去重
    // 引入规则级去重：避免重复的 action + subject 组合产生冗余 can 调用
    const dedupRules = new Set<string>();

    user?.roles.forEach((role) => {
      role.menus.forEach((menu) => {
        // path(/user) + acl('read,update,create,delete,manage) ——> actions
        // 对菜单内动作进行清洗与去重
        const actions = Array.from(
          new Set(
            menu.acl
              .split(",")
              .map((s) => s.trim())
              .filter(Boolean),
          ),
        );

        // 将 path 映射到实体/主题
        const subject = getEntitiesByPath(menu.path);
        // 生成稳定的 subject 标识用于去重
        const subjectKey = typeof subject === "function" ? (subject as any).name || "FunctionSubject" : String(subject);

        for (let i = 0; i < actions.length; i++) {
          const action = actions[i];
          const key = `${action}::${subjectKey}`;
          if (!dedupRules.has(key)) {
            can(action as any, subject as any);
            dedupRules.add(key);
          }
        }
      });
    });
    // can("read", Logs);
    // cannot("update", Logs);

    // 通过 build 生成最终的 ability 对象
    const ability = build({
      // 指定如何从对象实例检测出 subject 类型：使用构造函数名称（如 'User'、'Article'）
      detectSubjectType: (item) => item.constructor.name,
    });

    // 返回能力对象，可在守卫、服务、控制器中进行权限判断
    return ability;
  }
}
